package com.gagan.tplmatcher;

import java.util.ArrayList;
import java.util.List;

/**
 * @author gagan.goku@gmail.com
 */
public class PatternTree {
  private List<String> patterns;
  private List<?> data;

  public static class PatternData {
    public final int patternIdx;
    public final List<?> dataArray;

    public PatternData(int patternIdx, List<?> dataArray) {
      this.patternIdx = patternIdx;
      this.dataArray = dataArray;
    }
  }

  public PatternTree(List<String> patterns, List<?> data) {
    this.patterns = patterns;
    this.data = data;
  }

  public static String unescapePattern(String pattern) {
    String p = pattern;
    p = p.replaceAll("\\\\\\[", "[");
    p = p.replaceAll("\\\\\\]", "]");
    p = p.replaceAll("\\\\\\{", "{");
    p = p.replaceAll("\\\\\\}", "}");
    p = p.replaceAll("\\\\\\?", "?");
    p = p.replaceAll("\\\\\\(", "(");
    p = p.replaceAll("\\\\\\)", ")");
    p = p.replaceAll("\\\\\\|", "|");
    return p;
  }

  public static String stitchCompleteHtml(PatternTree patternTree) {
    String stitched = "";
    for (int i = 0; i < patternTree.data.size(); i++) {
      if (!(patternTree.data.get(i) instanceof String)) {
        // Pattern + data.
        PatternData data = (PatternData) patternTree.data.get(i);
        String regex = patternTree.patterns.get(data.patternIdx);
        List<?> dataArray = data.dataArray;
        Node r = parseRegex(regex);
        setMatchGroups(r, 0);

        String s = stitchHtml(r, dataArray);
        s = unescapePattern(s);
        stitched += s;
      } else {
        // String literal.
        stitched += (String) patternTree.data.get(i);
      }
    }
    return stitched;
  }

  public static boolean isEscaped(String str, int idx) {
    int count = 0;
    for (int i = idx-1; i >= 0; i--) {
      if (str.charAt(i) == '\\') {
        count++;
      } else {
        break;
      }
    }
    return count % 2 == 1;
  }

  public static final String CAPTURE = "CAPTURE";
  public static final String NO_CAPTURE = "NO_CAPTURE";
  public static final String CONCAT = "CONCAT";
  public static final String ALTERNATION = "ALTERNATION";
  public static final String STRING_LITERAL = "STRING_LITERAL";

  public static void getGroupInfo(String regex, List<List<Integer>> start,
                           List<List<Integer>> end,
                           List<Integer> alternation) {
    int group = 0;
    for (int i = 0; i < regex.length(); i++) {
      char c = regex.charAt(i);

      if (group < 0) {
        throw new RuntimeException("group < 0 : " + group);
      }

      if (c == '(' && !isEscaped(regex, i)) {
        if (start.size() <= group) {
          start.add(new ArrayList<Integer>());
          end.add(new ArrayList<Integer>());
        }
        start.get(group++).add(i);
      } else if(c == ')' && !isEscaped(regex, i)) {
        --group;
        if (end.size() <= group) {
          throw new RuntimeException("hello");
        }
        end.get(group).add(i);
      } else if(c == '|' && !isEscaped(regex, i) && group == 0) {
        alternation.add(i);
      }
    }
  }

  public static Node parseRegex(String regex) {
    List<List<Integer>> start = new ArrayList<List<Integer>>();
    List<List<Integer>> end = new ArrayList<List<Integer>>();
    List<Integer> alternation = new ArrayList<Integer>();

    getGroupInfo(regex, start, end, alternation);

    if (start.size() > 0 && start.get(0).size() == 1 && start.get(0).get(0) == 0 &&
        end.get(0).size() == 1 && end.get(0).get(0) == regex.length()-1) {
      // Case 1 - (?: ... ) or ( ... )
      Node node = new Node(CAPTURE);
      int begin = 1;
      if (regex.length() > 3 && regex.charAt(1) == '?' && regex.charAt(2) == ':') {
        node = new Node(NO_CAPTURE);
        begin = 3;
      }

      String r = regex.substring(begin, regex.length()-1);
      Node n = parseRegex(r);
      node.children.add(n);
      return node;
    } else if (alternation.size() > 0) {
      // Alternation
      alternation.add(regex.length());
      Node node = new Node(ALTERNATION);
      int prev = 0;
      for (int i = 0; i < alternation.size(); i++) {
        String r = regex.substring(prev, alternation.get(i));
        Node n = parseRegex(r);
        node.children.add(n);
        prev = alternation.get(i)+1;
      }
      return node;
    } else if(start.size() > 0 && start.get(0).size() > 0 && end.get(0).size() > 0) {
      // Concat
      Node node = new Node(CONCAT);
      List<Integer> s = start.get(0);
      List<Integer> e = end.get(0);
      s.add(regex.length());
      e.add(regex.length()-1);
      int prevEnd = 0;
      for (int i = 0; i < s.size(); i++) {
        String r = regex.substring(prevEnd, s.get(i));
        prevEnd = e.get(i)+1;

        if (!r.equals("")) {
          Node n = parseRegex(r);
          node.children.add(n);
        }

        r = regex.substring(s.get(i), e.get(i)+1);
        if (!r.equals("")) {
          Node n = parseRegex(r);
          node.children.add(n);
        }
      }
      return node;
    } else {
      // String literal.
      Node node = new Node(STRING_LITERAL);
      node.stringValue = regex;
      return node;
    }
  }

  public static int setMatchGroups(Node node, int groupIdx) {
    if (node.type.equals(ALTERNATION)) {
      for (int i = 0; i < node.children.size(); i++) {
        groupIdx = setMatchGroups(node.children.get(i), groupIdx);
      }
    } else if (node.type.equals(CAPTURE)) {
      node.matchGroup = groupIdx++;
    } else if (node.type.equals(NO_CAPTURE)) {
      if (node.children.size() != 1) {
        throw new RuntimeException("BADD");
      }
      return setMatchGroups(node.children.get(0), groupIdx);
    } else if (node.type.equals(CONCAT)) {
      for (int i = 0; i < node.children.size(); i++) {
        groupIdx = setMatchGroups(node.children.get(i), groupIdx);
      }
    }
    return groupIdx;
  }

  public static String stitchHtml(Node node, List<?> dataArray) {
    if (node.type.equals(ALTERNATION)) {
      for (int i = 0; i < node.children.size(); i++) {
        String s = stitchHtml(node.children.get(i), dataArray);
        if (s != null) {
          return s;
        }
      }
    } else if (node.type.equals(CAPTURE)) {
      try {
        return (String) dataArray.get(node.matchGroup);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    } else if (node.type.equals(NO_CAPTURE)) {
      if (node.children.size() != 1) {
        throw new RuntimeException("baaaad");
      }
      return stitchHtml(node.children.get(0), dataArray);
    } else if (node.type.equals(STRING_LITERAL)) {
      return node.stringValue;
    } else if (node.type.equals(CONCAT)) {
      String stitched = "";
      for (int i = 0; i < node.children.size(); i++) {
        String s = stitchHtml(node.children.get(i), dataArray);
        if (s == null) {
          return null;
        }
        stitched += s;
      }
      return stitched;
    }

    return null;
  }
}
